<?php
// +----------------------------------------------------------------------
// | SentCMS [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2013 http://www.tensent.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: molong <molong@tensent.cn> <http://www.tensent.cn>
// +----------------------------------------------------------------------
namespace tensent\yspay\BasicService;

use GuzzleHttp\HandlerStack;
use Mockery\CountValidator\Exact;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use tensent\yspay\Kernel\BaseClient;
use Rtgm\sm\RtSm2;

class Client extends BaseClient{
	/**
	 * 签名加密
	 * @param $input
	 * @return string
	 * @author tu6ge
	 * @date 2019-08-05 16:51
	 */
	public function signEncrypt($input){
		$pkcs12 = file_get_contents($this->app->config->private_cert); //私钥
		// if (openssl_pkcs12_read($pkcs12, $certs, $this->app->config->pfxpassword) == false) {
		// 	throw new \Exception('openssl_pkcs12_read fail');
		// }
		$privateKey = openssl_get_privatekey($pkcs12);
		$signedMsg = "";
		if (openssl_sign($input, $signedMsg, $privateKey, OPENSSL_ALGO_SHA1) == false) {
			throw new \Exception('openssl_sign fail');
		}
		return base64_encode($signedMsg);
	}

	/**
	 * 支持国密SM2签名
	 * 在java中读取sm2文件实现签名返回给PHP
	 * 需要php-java-bridge
	 *  https://php-java-bridge.sourceforge.net/pjb/index.php
	 * @param array $input 参与签名的参数数组
	 * @return string
	 */
	public function SignEncryptBySM2($input){
		echo __DIR__.'\Java.inc';
		define("JAVA_DEBUG", true); //调试设置
		define("JAVA_HOSTS", "127.0.0.1:8080"); //设置javabridge监听端口
		include_once(__DIR__.'\Java.inc');
		java_set_file_encoding("UTF-8"); //设置JAVA编码。
		try {
			$sm2=__DIR__.'\guoshut.sm2';
			$java=java('org.yncc.ysepay.Main');
			$sign=$java->sign($sm2, $input);
			$val=java_values($sign);
			var_dump($val);


			return $val;
		} catch (\Throwable $th) {
			echo $th->getMessage();
			throw new \Exception($th->getMessage());
		}
	}
	/**
	 * 获取待签名字符串
	 * @param $myParams
	 * @return string
	 * @author tu6ge
	 * @date dtime
	 */
	public function signStr(array $myParams, bool $no_empty=false):string {
		ksort($myParams);
		$signStr = "";
		foreach ($myParams as $key => $val) {
			if($no_empty){
				if($val){
					$signStr .= $key . '=' . $val . '&';
				}
			}else{
				$signStr .= $key . '=' . $val . '&';
			}
		}
		return rtrim($signStr, '&');
	}
	public function http_build_query($myParams, $no_empty=false) {
		$signStr = "";
		foreach ($myParams as $key => $val) {
			if($no_empty){
				if($val){
					$signStr .= $key . '=' . $val . '&';
				}
			}else{
				$signStr .= $key . '=' . $val . '&';
			}
		}
		return rtrim($signStr, '&');
	}
	/**
	 * 异步回调的签名验证
	 * @param $sign
	 * @param $data
	 * @return int
	 * @author tu6ge
	 * @date dtime
	 */
	public function signCheck($sign, $data) {
		if($this->app->config->sign_type=='RSA'){
		$certificateCAcerContent = file_get_contents($this->app->config->businessgatecerpath);
		$certificateCApemContent = '-----BEGIN CERTIFICATE-----' . PHP_EOL . chunk_split(base64_encode($certificateCAcerContent), 64, PHP_EOL) . '-----END CERTIFICATE-----' . PHP_EOL;
		// 签名验证
		return openssl_verify($data, base64_decode($sign), openssl_get_publickey($certificateCApemContent), OPENSSL_ALGO_SHA1);
		}else if($this->app->config->sign_type=='SM'){
			$publicKey=file_get_contents($this->app->config->businessgatecerpath);
			$sm2=new RtSm2('base64',true);
			$ret=$sm2->verifySign($data,$sign,$publicKey);
			var_dump($ret);
			return $ret;
		}
	}

	public function buildUpSign(array $data):array {
		unset($data['sign']);
		$signData = [];
		foreach ($data as $key => $val) {
			if($key != 'bizReqJson'){
				$signData[$key] = $val;
			}
		}
		if($this->app->config->sign_type=='RSA'){
			$sign = $this->signEncrypt(
				$this->signStr($signData)
			);
		}else if($this->app->config->sign_type=='SM'){
			$sign=$this->SignEncryptBySM2($signData);
		}else {
			throw new \Exception('请设置加密方式');
		}
		$data['sign'] = $sign;
		return $data;
	}

	public function buildSign(array $data):array {
		unset($data['sign']);
		if($this->app->config->sign_type=='RSA'){
			$sign = $this->signEncrypt(
				$this->signStr($data)
			);
		}else if($this->app->config->sign_type=='SM'){
			$sign=$this->SignEncryptBySM2($data);
		}else {
			throw new \Exception('请设置加密方式');
		}
		$data['sign'] = $sign;
		return $data;
	}

	public function httpPost($url, $params, $result_field="") {
		return $this->request($url, 'POST', ['form_params'=>$params], $result_field);
	}

	public function httpGet($url) {
		return $this->request($url);
	}

	/**
	 * Upload file.
	 *
	 * @param string $url
	 * @param array $params
	 * @param string $result_field
	 *
	 */
	public function httpUpload($url, $params, $result_field=""){
		$handlerStack = HandlerStack::create();
		$handlerStack->push($this->publicParamsMiddleware());
		$handlerStack->push($this->upSignMiddleware());

		empty($options['handler']) && $options['handler'] = $handlerStack;

		$this->app->logger->info('request:',['POST',$url,isset($options['form_params'])?$options['form_params']:[]]);
		$options['form_params'] = $params;
		$response = $this->app->http_client->request('POST', $url, $options);
		if(isset($this->app->config->response_type) && $this->app->config->response_type=='raw'){
			return $response;
		}

		$res = $response->getBody()->getContents();
		try {
			$res = \GuzzleHttp\json_decode($res, true);
			$this->app->logger->info('response',['res'=>$res]);
		} catch (\Exception $th) {
			$this->app->logger->info("response json_decode err",['res'=>$res,'err'=>$th->getMessage()]);
			throw new \Exception($th->getMessage());
		}

		if(empty($result_field)){
			return $res;
		}
		if(!isset($res[$result_field])){
			throw new \Exception('return data is fail');
		}
		return $res[$result_field] ?? [];
	}


	/**
	 * 公共参数 中间件
	 * @return \Closure
	 * @author tu6ge
	 * @date 2019/8/13 下午9:34
	 */
	public function publicParamsMiddleware() {
		return function (callable $handler){
			return function (RequestInterface $request, array $options) use ($handler) {
				$params = $this->publicParams();
				$allUrl = $this->getUrl();
				$request->getBody()->rewind();
				$con = $request->getBody()->getContents();
				parse_str($con, $form_params);
				$form_params = array_merge($form_params, $params);
				$request->getBody()->rewind();
				$request->getBody()->write(http_build_query($form_params, '&'));
				return $handler($request, $options);
			};
		};
	}

	/**
	 * 给guzzle添加sign中间件
	 * @return \Closure
	 * @author tu6ge
	 * @date 2019/8/12 下午9:18
	 */
	public function signMiddleware() {
		return function (callable $handler) {
			return function (RequestInterface $request, array $options) use ($handler) {
				$request->getBody()->rewind();
				$con = $request->getBody()->getContents();
				parse_str($con, $form_params);
				$this->app->logger->info('build sign params:',$form_params);
				$form_params = $this->buildSign($form_params);
				$request->getBody()->rewind();
				$request->getBody()->write(http_build_query($form_params, '&'));

				return $handler($request, $options);
			};
		};
	}

	public function upSignMiddleware() {
		return function (callable $handler) {
			return function (RequestInterface $request, array $options) use ($handler) {
				$request->getBody()->rewind();
				$con = $request->getBody()->getContents();
				parse_str($con, $form_params);
				$this->app->logger->info('build sign params:',$form_params);
				$form_params = $this->buildUpSign($form_params);
				$request->getBody()->rewind();
				$request->getBody()->write(http_build_query($form_params, '&'));

				return $handler($request, $options);
			};
		};
	}

	/**
	 * @param $url
	 * @param string $method
	 * @param array $options
	 * @param string $result_field
	 * @return array|mixed|ResponseInterface|string
	 * @throws ResponseException
	 * @throws YsepayException
	 * @throws \GuzzleHttp\Exception\GuzzleException
	 * @author tu6ge
	 * @date 2019/8/13 下午8:59
	 */
	public function request($url, $method = 'GET', $options = [], $result_field='') {
		$method = strtoupper($method);

		$handlerStack = HandlerStack::create();
		$handlerStack->push($this->publicParamsMiddleware());
		$handlerStack->push($this->signMiddleware());

		empty($options['handler']) && $options['handler'] = $handlerStack;
		$this->app->logger->info('request:',[$method,$url,isset($options['form_params'])?$options['form_params']:[]]);
		$response = $this->app->http_client->request($method, $url, $options);
		if(isset($this->app->config->response_type) && $this->app->config->response_type=='raw'){
			return $response;
		}

		$res = $response->getBody()->getContents();
		try {
			$res = \GuzzleHttp\json_decode($res, true);
			$this->app->logger->info('response',['res'=>$res]);
		} catch (\Exception $th) {
			$this->app->logger->info("response json_decode err",['res'=>$res,'err'=>$th->getMessage()]);
			throw new \Exception($th->getMessage());
		}

		if(empty($result_field)){
			return $res;
		}
		if(!isset($res[$result_field])){
			throw new \Exception('return data is fail');
		}
		return $res[$result_field] ?? [];
	}
}
